from django.contrib.auth.models import User
from django.contrib.contenttypes import generic
from django.contrib.contenttypes.models import ContentType
from django.db import models
from django.db.utils import IntegrityError
import datetime
from django.utils import unittest


class Tag(models.Model):
    user = models.ForeignKey(User)
    type = models.CharField(max_length=100)
    name = models.CharField(max_length=100)

    @staticmethod
    def category(name, user):
        return Tag(type="CATEGORY", name=name, user=user)

    @staticmethod
    def task_tag(name, user):
        return Tag(type="TASK", name=name, user=user)

    @staticmethod
    def journal_tag(name, user):
        return Tag(type="JOURNAL", name=name, user=user)

    @staticmethod
    def categories(user):
        return Tag.objects.filter(user=user, type="CATEGORY")

    @staticmethod
    def task_tags(user):
        return Tag.objects.filter(user=user, type="TASK")

    @staticmethod
    def journal_tags(user):
        return Tag.objects.filter(user=user, type="JOURNAL")

    def __unicode__(self):
        return "[%s] %s" % (self.type, self.name)

    class Meta:
        unique_together = ("type", "name")


class EntityTag(models.Model):
    """
        Tag -> Entity
    """
    tag = models.ForeignKey(Tag)
    entity_id = models.PositiveIntegerField()

    @staticmethod
    def categories(goal):
        return EntityTag.objects.filter(entity_id=goal.pk, tag__type="CATEGORY")

    @staticmethod
    def task_tags(task):
        return EntityTag.objects.filter(entity_id=task.pk, tag__type="TASK")

    @staticmethod
    def journal_tags(journal):
        return EntityTag.objects.filter(entity_id=journal.pk, tag__type="JOURNAL")

    def __unicode__(self):
        return u'%s - %s' % (self.entity_id, self.tag)

    class Meta:
        unique_together = ("tag", "entity_id")


class Attachment(models.Model):
    file = models.FileField(upload_to="attachments")

    entity_type = models.ForeignKey(ContentType)
    entity_id = models.PositiveIntegerField()
    entity = generic.GenericForeignKey('entity_type', 'entity_id')

    def __unicode__(self):
        return unicode(self.file)


class BaseTask(models.Model):
    name = models.CharField(max_length=100)
    created_date = models.DateField(auto_now_add=True)
    start_date = models.DateField(blank=True, null=True)
    completed_date = models.DateField(blank=True, null=True)
    due_date = models.DateField(blank=True, null=True)

    def __unicode__(self):
        return unicode(self.name)


class Goal(BaseTask):
    user = models.ForeignKey(User, null=True)
    parent = models.ForeignKey('self', blank=True, null=True)
    description = models.TextField()
    show_on_dashboard = models.BooleanField()
    attachments = generic.GenericRelation(Attachment,
        content_type_field='entity_type',
        object_id_field='entity_id')

    def __init__(self, *args, **kwargs):
        super(Goal, self).__init__(*args, **kwargs)

    def __category_set(self):
        return EntityTag.objects.filter(entity_id=self.pk, tag__type="CATEGORY")

    def __completed_tasks(self):
        return self.task_set.filter(completed_date__isnull=False)

    def __json(self):
        return {'name': self.name, 'description': self.description, 'start_date': self.start_date.isoformat(),
                'due_date': self.due_date.isoformat(), 'show_on_dashboard': self.show_on_dashboard,
                'parent': self.parent_id}

    def __categories(self):
        cats = self.category_set.all()
        if not len(cats):
            return ''
        return reduce(lambda x, y: '%s, %s' % (x, y.tag.name), cats[1:], cats[0].tag.name)

    completed_task_set = property(__completed_tasks)
    category_set = property(__category_set)
    categories = property(__categories)
    as_json = property(__json)


class _Recurring(models.Model):
    type = models.CharField(max_length=1)
    value = models.SmallIntegerField(blank=True, null=True)

    def __unicode__(self):
        if self.type == 'D':
            return u"Daily"
        if self.type == 'W':
            return u"Weekly(%s)" % self.value
        if self.type == 'M':
            return u"Monthly(%s)" % self.value


class Recurring():
    def __init__(self):
        raise AssertionError("Use Recurring daily, weekly, monthly methods instead!")

    @staticmethod
    def daily():
        return _Recurring(type='D')

    @staticmethod
    def weekly(day):
        return _Recurring(type='W', value=day)

    @staticmethod
    def monthly(day):
        return _Recurring(type='M', value=day)


class Task(BaseTask):
    user = models.ForeignKey(User, null=True)
    goal = models.ForeignKey(Goal, blank=True, null=True)
    priority = models.SmallIntegerField(blank=True, null=True)
    note = models.TextField()
    spent_time = models.TimeField(blank=True, null=True)
    estimated_time = models.TimeField(blank=True, null=True)
    recurring = models.ForeignKey(_Recurring, blank=True, null=True)

    def tag_set(self):
        return EntityTag.objects.filter(entity_id=self.pk)


class Purpose(models.Model):
    goal = models.ForeignKey(Goal)
    text = models.TextField()
    attachments = generic.GenericRelation(Attachment,
        content_type_field='entity_type',
        object_id_field='entity_id')

    def __unicode__(self):
        return unicode(self.text)


class Habit(models.Model):
    user = models.ForeignKey(User)
    name = models.CharField(max_length=200)
    days_per_week = models.SmallIntegerField()
    repetitions = models.IntegerField()
    goal = models.ForeignKey(Goal, blank=True, null=True)
    show_on_dashboard = models.BooleanField()

    def __unicode__(self):
        return unicode(self.name)


class HabbitCheck(models.Model):
    habit = models.ForeignKey(Habit)
    date = models.DateField(auto_now_add=True)


class Journal(models.Model):
    user = models.ForeignKey(User)
    goal = models.ForeignKey(Goal, blank=True, null=True)
    title = models.TextField() # TODO encrytion
    content = models.TextField() # TODO encrytion
    created_date = models.DateField(auto_now_add=True)
    last_mod = models.DateField(auto_now=True)

    def __tag_set(self):
        return EntityTag.objects.filter(entity_id=self.pk, tag__type="JOURNAL")

    def __tags(self):
        cats = self.tag_set.all()
        if not len(cats):
            return ''
        return reduce(lambda x, y: '%s, %s' % (x, y.tag.name), cats[1:], cats[0].tag.name)

    tags = property(__tags)
    tag_set = property(__tag_set)


class HabitTest(unittest.TestCase):
    def setUp(self):
        pass

    def tearDown(self):
        pass

    def test_name(self):
        self.assertEqual(unicode(Habit(name="Test habit")), u"Test habit")


class GoalTest(unittest.TestCase):
    def setUp(self):
        self.user = User()
        self.user.save()

    def tearDown(self):
        Tag.objects.all().delete()
        Goal.objects.all().delete()
        User.objects.all().delete()

    def test_default_fields(self):
        """
            It should instantiate without some default fields
        """
        goal = Goal.objects.create(name="test", start_date=datetime.datetime.now())

    def test_categories(self):
        goal = Goal.objects.create(name="test", start_date=datetime.datetime.now())
        self.assertEqual(len(goal.category_set.all()), 0)
        tag1 = Tag.category("a", self.user)
        tag1.save()
        tag2 = Tag.category("b", self.user)
        tag2.save()
        tag3 = Tag.category("c", self.user)
        tag3.save()
        EntityTag(entity_id=goal.pk, tag=tag1).save()
        EntityTag(entity_id=goal.pk, tag=tag2).save()
        EntityTag(entity_id=goal.pk, tag=tag3).save()
        self.assertEqual(len(goal.category_set.all()), 3)

    def test_completed_task_set(self):
        goal = Goal.objects.create(name="test", start_date=datetime.datetime.now())
        self.assertEqual(len(goal.completed_task_set.all()), 0)
        Task.objects.create(start_date=datetime.datetime.now(), goal=goal, completed_date=datetime.datetime.now())
        Task.objects.create(start_date=datetime.datetime.now(), goal=goal, completed_date=datetime.datetime.now())
        Task.objects.create(start_date=datetime.datetime.now(), goal=goal, completed_date=datetime.datetime.now())
        Task.objects.create(start_date=datetime.datetime.now(), goal=goal)
        self.assertEqual(len(goal.task_set.all()), 4)
        self.assertEqual(len(goal.completed_task_set.all()), 3)

    def test_goal_empty_categories(self):
        goal = Goal.objects.create(name="test", start_date=datetime.datetime.now())
        self.assertEqual(goal.categories, '')

    def test_goal_categories(self):
        goal = Goal.objects.create(name="test", start_date=datetime.datetime.now())
        tag1 = Tag.category("a", self.user)
        tag1.save()
        tag2 = Tag.category("b", self.user)
        tag2.save()
        tag3 = Tag.category("c", self.user)
        tag3.save()
        EntityTag(entity_id=goal.pk, tag=tag1).save()
        self.assertEqual(goal.categories, 'a')
        EntityTag(entity_id=goal.pk, tag=tag2).save()
        self.assertEqual(goal.categories, 'a, b')
        EntityTag(entity_id=goal.pk, tag=tag3).save()
        self.assertEqual(goal.categories, 'a, b, c')


class RecurringTest(unittest.TestCase):
    def setUp(self):
        pass

    def tearDown(self):
        pass

    def test_private_constructor(self):
        self.assertRaises(AssertionError, Recurring)

    def test_instantiate(self):
        rec = Recurring.daily()
        self.assertTrue(rec.type == 'D' and rec.value is None)

        rec = Recurring.weekly(3)
        self.assertTrue(rec.type == 'W' and rec.value == 3)

        rec = Recurring.monthly(13)
        self.assertTrue(rec.type == 'M' and rec.value == 13)

    def test_name(self):
        self.assertEqual(unicode(Recurring.daily()), u"Daily")
        self.assertEqual(unicode(Recurring.weekly(3)), u"Weekly(3)")
        self.assertEqual(unicode(Recurring.monthly(13)), u"Monthly(13)")


class TaskTest(unittest.TestCase):
    def setUp(self):
        self.user = User()
        self.user.save()

    def tearDown(self):
        User.objects.all().delete()
        Tag.objects.all().delete()

    def test_tags(self):
        task = Task.objects.create(start_date=datetime.datetime.now())
        self.assertEqual(len(task.tag_set()), 0)
        tag1 = Tag.task_tag("a", self.user)
        tag1.save()
        tag2 = Tag.task_tag("b", self.user)
        tag2.save()
        tag3 = Tag.task_tag("c", self.user)
        tag3.save()
        EntityTag(entity_id=task.pk, tag=tag1).save()
        EntityTag(entity_id=task.pk, tag=tag2).save()
        EntityTag(entity_id=task.pk, tag=tag3).save()
        Tag.objects.create(type="C", name="d", user=self.user)
        self.assertEqual(len(task.tag_set()), 3)

    def test_all_tag(self):
        self.assertEqual(len(Tag.task_tags(self.user)), 0)
        Tag.task_tag("a", self.user).save()
        Tag.task_tag("b", self.user).save()
        Tag.objects.create(type="B", name="c", user=self.user)
        Tag.objects.create(type="C", name="d", user=self.user)
        self.assertEqual(len(Tag.task_tags(self.user)), 2)

    def test_usual_things(self):
        task = Task.objects.create(name="Test Task", start_date=datetime.datetime.now())
        self.assertEqual(unicode(task), u"Test Task")


class TagTest(unittest.TestCase):
    def setUp(self):
        self.user = User()
        self.user.save()
        self.test_goal = Goal.objects.create(name="test", start_date=datetime.datetime.now())

    def tearDown(self):
        Tag.objects.all().delete()
        Goal.objects.all().delete()
        User.objects.all().delete()

    def test_tag_unique_keys(self):
        """
            The Tag table contains combined unique-keys: 'type' with 'name'
        """
        purp = Purpose.objects.create(goal=self.test_goal, text="test text")
        Tag.objects.create(type="A", name="a", user=self.user)
        Tag.objects.create(type="A", name="b", user=self.user)
        Tag.objects.create(type="B", name="a", user=self.user)
        Tag.objects.create(type="B", name="b", user=self.user)
        self.assertRaises(IntegrityError, Tag.objects.create, type="A", name="a")
        self.assertRaises(IntegrityError, Tag.objects.create, type="B", name="b")

    def test_tag_non_nulls(self):
        self.assertRaises(IntegrityError, Tag.objects.create, type=None, name="a")
        self.assertRaises(IntegrityError, Tag.objects.create, type="a", name=None)
        self.assertRaises(IntegrityError, Tag.objects.create, type=None, name=None)

    def test_usual_things(self):
        tag = Tag(type="FILE", name="asd.jpg")
        self.assertEqual(unicode(tag), u"[FILE] asd.jpg")
        # TODO user

    def test_entity_usual_things(self):
        goal = Goal.objects.create(name="Goal", start_date=datetime.datetime.now())
        tag = EntityTag(entity_id=goal.pk, tag=Tag(type="FILE", name="asd.jpg"))
        self.assertEqual(unicode(tag), u"%d - [FILE] asd.jpg" % goal.pk)


    def test_get_tags(self):
        tags = Tag.objects.filter(type="TEST")
        self.assertTrue(len(tags) == 0)
        Tag.objects.create(type="TEST", name="a", user=self.user)
        Tag.objects.create(type="TEST", name="b", user=self.user)
        Tag.objects.create(type="TEST", name="c", user=self.user)
        Tag.objects.create(type="TEST", name="d", user=self.user)
        tags = Tag.objects.filter(type="TEST")
        self.assertTrue(len(tags) == 4)

    def test_static_methods(self):
        self.assertEqual(len(Tag.categories(self.user)), 0)
        self.assertEqual(len(Tag.task_tags(self.user)), 0)
        Tag.objects.create(type="CATEGORY", name="a", user=self.user)
        Tag.objects.create(type="CATEGORY", name="b", user=self.user)
        Tag.objects.create(type="TASK", name="c", user=self.user)
        Tag.objects.create(type="C", name="d", user=self.user)
        # Tag.categories
        self.assertEqual(len(Tag.categories(self.user)), 2)
        self.assertEqual(len(Tag.task_tags(self.user)), 1)


class PurposeTest(unittest.TestCase):
    def setUp(self):
        self.test_goal = Goal.objects.create(name="test", start_date=datetime.datetime.now())

    def tearDown(self):
        Tag.objects.all().delete()
        Goal.objects.all().delete()

    def test_attachments(self):
        purp = Purpose.objects.create(goal=self.test_goal, text="test text")
        att = Attachment(entity=purp)
        att.save()
        att = Attachment(entity=purp)
        att.save()
        att = Attachment(entity=purp)
        att.save()
        self.assertEqual(len(purp.attachments.all()), 3)

    def test_usual_things(self):
        purp = Purpose(text="test purpose")
        self.assertEqual(unicode(purp), u"test purpose")


class AttachmentTest(unittest.TestCase):
    def setUp(self):
        pass

    def test_usual_things(self):
        a = Attachment(file="test.file")
        self.assertEqual(unicode(a), u"test.file")